package fhq.hcmute.qrcode.decoder;

import fhq.hcmute.qrcode.common.BitMatrix;
import fhq.hcmute.qrcode.common.DataBlock;
import fhq.hcmute.qrcode.common.ErrorCorrectionLevel;
import fhq.hcmute.qrcode.common.MaskPattern;
import fhq.hcmute.qrcode.common.Version;
import fhq.hcmute.qrcode.encoder.BitArray;
import fhq.hcmute.qrcode.util.QRCodeException;
/**
 * @author Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
public class MatrixConverter {
	private BitMatrix dataMatrix;
	private Version version;
	private FormatInformation formatInformation;

	public MatrixConverter(BitMatrix dataMatrix) throws QRCodeException {
		this.dataMatrix = dataMatrix;
		this.version = getVersionInformation();
		this.formatInformation = readFormatInformation();
	}

	private Version getVersionInformation() throws QRCodeException {
		int dimension = dataMatrix.getModule();
		int preVersion = (dimension - 17) / 4;
		if (preVersion <= 6) {
			return Version.getVersionByNumber(preVersion);
		}
		BitArray versionBitArray = new BitArray();
		try {
			// try to decode version information in lower left.
			for (int i = 5; i >= 0; i--) {
				for (int j = 2; j >= 0; j--) {
					versionBitArray.append(dataMatrix.getBitAt(i, dimension
							- 11 + j));
				}
			}
			return Version.decode(versionBitArray);
		} catch (QRCodeException e) {
			// Try to decode version information in upper right( Failed in lower
			// left).
			for (int i = 5; i >= 0; i--) {
				for (int j = 2; j >= 0; j--) {
					versionBitArray.append(dataMatrix.getBitAt(dimension - 11
							+ j, i));
				}
			}
			return Version.decode(versionBitArray);
		}
	}

	private BitMatrix buildTemplateMatrix() {
		BitMatrix dataMatrix = new BitMatrix(version);
		int[] fi = { BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE };
		int[] vi = { BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE,
				BitMatrix.TRUE, BitMatrix.TRUE, BitMatrix.TRUE };
		dataMatrix.setFormatInformation(fi);
		if (this.version.getVersionNumber() > 6) {
			dataMatrix.setVersionInformation(vi, MaskPattern.Verion_0);
		}
		return dataMatrix;
	}

	private BitArray readCodewords(BitMatrix matrixPattern, int totalBit) {
		BitArray bitArray = new BitArray();
		int y = matrixPattern.getModule() - 1;
		int direction = -1;
		int bitIndex = 0;
		for (int x = matrixPattern.getModule() - 1; x > 0; x -= 2) {
			// Ignore the vertical timing.
			if (x == 6) {
				x -= 1;
			}
			while (y >= 0 && y <= matrixPattern.getModule() - 1) {
				for (int i = 0; i < 2; i++) {
					if (matrixPattern.get(y, x - i) != BitMatrix.EMPTY) {
						continue;
					}
					if (bitIndex == totalBit) {
						return bitArray;
					}
					bitArray.append(this.getDataMatrix().getBitAt(y, x - i));
					bitIndex++;
				}
				y += direction;
			}
			direction = -direction;
			y += direction;
		}
		return bitArray;
	}

	public DataBlock readContents() throws QRCodeException {
		BitMatrix patternMatrix = buildTemplateMatrix();
		this.dataMatrix = formatInformation.getMaskPattern()
				.releaseMaskPattern(dataMatrix);
		int[] codewords = readCodewords(patternMatrix,
				version.getTotalNumberOfCodewords() * 8).getIntegerArray();
		return DataBlock.parse(codewords, version,
				formatInformation.getCorrectionLevel());
	}

	private FormatInformation readFormatInformation() {
		int dimension = dataMatrix.getModule();
		BitArray bitArray = new BitArray();
		for (int i = 14; i >= 0; i--) {
			if (i < 8) {
				if (i <= 5) {
					bitArray.append(dataMatrix.getBitAt(i, 8));
					continue;
				}
				bitArray.append(dataMatrix.getBitAt(i + 1, 8));
			} else {
				bitArray.append(dataMatrix.getBitAt(dimension - 7 + i - 8, 8));
			}
		}
		// Xor result with special bits.
		bitArray.xor("101010000010010");

		ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel
				.parse(bitArray.getRange(0, 1).toString());

		MaskPattern maskPattern = MaskPattern.parse(bitArray.getRange(2, 4)
				.toString());

		return new FormatInformation(errorCorrectionLevel, maskPattern);
	}

	public BitMatrix getDataMatrix() {
		return dataMatrix;
	}

	public void setDataMatrix(BitMatrix dataMatrix) {
		this.dataMatrix = dataMatrix;
	}

	public FormatInformation getFormatInformation() {
		return formatInformation;
	}

	public Version getVersion() {
		return version;
	}

}
